
import random
from xlwt import Workbook
from matplotlib import pyplot as plt
from pandas import read_excel, DataFrame
from numpy import zeros, array, arange, hstack
from numpy.random import choice, rand, randint

excel = read_excel('基础数据表.xls')
Q = [_ for _ in list(excel['出库频次']) if _ != NaN]
K = [_ for _ in list(excel['货物品类编号']) if _ != NaN]
M = [_ for _ in list(excel['货物重量']) if _ != NaN]
a, h, c, g, miu, A, B, C, w1, w2, q_ = list(excel['模型参数'][:11])
# 货格长度、货位高度、通道宽度、重力加速度、地面摩擦系数、x方向有A排、y方向有B列、z方向有C层、第一个规则的权值、第二个规则的权值
epochs, n_variables, cross_rate, mutate_rate = list(excel['算法参数'][:4])
n_goods, goods = len(K), []  # 货物的数量
A, B, C, epochs, n_variables = int(A), int(B), int(C), int(epochs), int(n_variables)  # 取整数


class Goods:  # 商品类
    def __init__(self, i, x, y, z, k, m, q):
        self.i = i  # 编号
        self.x = x  # 排数
        self.y = y  # 列数
        self.z = z  # 层数
        self.k = k  # 种类
        self.m = m  # 质量
        self.q = q  # 频率
        self.mid_x = 0  # 这类商品的中心x坐标
        self.mid_y = 0  # 这类商品的中心y坐标
        self.mid_z = 0  # 这类商品的中心z坐标

    def get(self):
        return [self.i, self.x, self.y, self.z, self.k, self.m, self.q, self.mid_x, self.mid_y, self.mid_z]


class GA:
    def __init__(self, m, n, func):
        code = []  # 生成初始种群
        nums = zeros((m, 3 * n))
        coo = zeros((m, n, 3))  # 以坐标的形式展现出来
        for i in range(m):
            for j in range(n):
                code.append(random.sample(range(0, A * B * C), n))
        code = array(code)
        for i in range(m):
            for j in range(n):
                nums[i, j + n] = code[i, j] // (A * C)
                copy = nums[i, j + n]
                nums[i, j + 2 * n] = (code[i, j] - copy * (A * C)) // A
                cop = nums[i, j + 2 * n]
                nums[i, j] = code[i, j] - cop * A - copy * A * C
                coo[i, j] = [nums[i, j], nums[i, j + n], nums[i, j + 2 * n]]
        nums = array(nums)
        m = nums.shape[0]
        n = nums.shape[1]
        pop = zeros((m, n // 3))
        for i in range(m):
            for j in range(n // 3):  # 编码方式：
                pop[i, j] = nums[i, j] + nums[i, j + n // 3] * A * C + nums[i, j + 2 * n // 3] * A
        self.POP = pop
        self.cross_rate = cross_rate
        self.mutation = mutate_rate
        self.func = func
        self.h = []

    def translate_dna(self):  # 将编码后的DNA翻译回来（解码）
        pop = self.POP
        m = pop.shape[0]
        n = pop.shape[1]
        vector = zeros((m, 3 * n))
        for i in range(m):
            for j in range(n):
                vector[i, j + n] = pop[i, j] // (A * C)
                copy = vector[i, j + n]
                vector[i, j + 2 * n] = (pop[i, j] - copy * (A * C)) // A
                cop = vector[i, j + 2 * n]
                vector[i, j] = pop[i, j] - cop * A - copy * A * C
        return vector

    def get_fitness(self):  # 得到适应度
        return q_ / self.func(self.translate_dna())

    def select(self):  # 自然选择
        fitness = self.get_fitness()[0]
        self.POP = self.POP[
            choice(arange(self.POP.shape[0]), size=self.POP.shape[0], replace=True, p=fitness / sum(fitness))]

    def crossover(self):  # 染色体交叉
        for people in self.POP:
            if rand() < self.cross_rate:
                i_ = randint(0, self.POP.shape[0], size=1)
                dd = randint(0, len(people), size=randint(0, len(people)))
                for d in dd:
                    if self.POP[i_, d] not in people:
                        people[d] = self.POP[i_, d]

    def mutate(self):  # 基因变异
        for people in self.POP:
            for point in range(len(people)):
                if rand() < self.mutation:
                    x = randint(0, A * B * C)
                    people[point] = x if x not in people else people[point]

    def evolution(self):  # 进化
        self.select()
        self.crossover()
        self.mutate()

    def log(self):  # 打印当前状态日志
        d1 = self.translate_dna()
        d2 = self.get_fitness().reshape((len(self.POP), 1))
        d3 = hstack((d1, d2))
        return DataFrame(d3)


def out(all_goods):  # 输出为excel
    book = Workbook(encoding='utf-8')
    worksheet = book.add_sheet('结果')
    sheet = [
        ['货物编号', '排', '列', '层', '货物品类编号', '货物重量', '出库频次', '该类中心x', '该类中心y', '该类中心z']]
    for good in all_goods:
        sheet.append(good.get())
    for line in range(len(sheet)):
        for row in range(len(sheet[0])):
            worksheet.write(line, row, sheet[line][row])
    book.save('货位分配结果表.xls')
    print('已保存 货位分配结果表.xls')


def show(history):
    plt.plot([i for i in range(len(history))], history)
    plt.xlabel('epoch')
    plt.ylabel('value')
    plt.savefig('迭代优化图.png')
    plt.show()
    print('已保存 迭代优化图.png')


def aim_function(matrix):
    global goods
    values = []
    for xyz in matrix:
        goods = [Goods(i=j + 1, x=xyz[j] + 1, y=xyz[j + n_goods] + 1, z=xyz[j + 2 * n_goods] + 1,
                       k=K[j], m=M[j], q=Q[j]) for j in range(n_goods)]
        w_0 = sum([good.m * g * good.q * miu * ((good.x / 2 - 1) * (2 * a + c) + a + c / 2) for good in goods])
        w_1 = sum([good.m * g * good.q * ((good.z - 1) * h) for good in goods])
        w_ = w_0 + w_1
        for class_num in range(len(set(good.k for good in goods))):
            x = sum([good.x for good in goods if good.k == class_num])
            y = sum([good.y for good in goods if good.k == class_num])
            z = sum([good.z for good in goods if good.k == class_num])
            n = sum([1 for good in goods if good.k == class_num])
            for good in goods:
                if good.k == class_num:
                    good.mid_x, good.mid_y, good.mid_z = x / n, y / n, z / n
        d_ = sum([((good.mid_x - good.x) ** 2 + (good.mid_y - good.y) ** 2 + (good.mid_z - good.z) ** 2) ** (1 / 2)
                  for good in goods])
        values.append(w1 * w_ + w2 * d_)
    return array([values])


ga = GA(m=n_variables, n=n_goods, func=aim_function)
for epoch in range(epochs):
    ga.evolution()
    res = array(ga.log())
    rs = aim_function(res).tolist()[0]
    value = max(rs)
    index = rs.index(value)
    if len(ga.h) > 0:
        value = value if value < min(ga.h) else min(ga.h)
    ga.h.append(value)
    res = res[index]
    print(f'轮次：{epoch + 1}/{epochs} 最优解：{value}')
    result = [[int(o) for o in [res[one], res[one + n_goods], res[one + n_goods * 2]]] for one in range(n_goods)]

out(all_goods=goods)
show(ga.h)
